# python-resmon - Python tools for Resmon monitoring and the Circonus API
# Copyright (c) 2010 Crown coypright
# 
# This file is part of python-resmon.
# 
# python-resmon is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# python-resmon is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with python-resmon.  If not, see <http://www.gnu.org/licenses/>.

"""Clean up the presentation of Circonus alerts"""
import os
import yaml
import re
import fnmatch


class AlertFormatter(object):
    def __init__(self, descriptions_yaml="metrics.yaml"):
        self.mapfile = descriptions_yaml
        self.map_mtime = 0
        self.reload()

    def reload(self):
        """Reload the metric descriptions, if it has changed."""
        mtime = os.path.getmtime(self.mapfile)
        if mtime > self.map_mtime:
            try:
                self.namemap = yaml.load(open(self.mapfile))
            except IOError:
                self.namemap = {}
        self.map_mtime = mtime

    def format_alert(self, host, metric, value, cleared=False):
        """Format one alert"""
        fmetric = self.format_name(metric)
        if value == 'null':
            if cleared:
                return "%s: %s restored." % (host, fmetric)
            else:
                return "%s: %s has failed!" % (host, fmetric)
        else:
            if cleared:
                return "%s: %s cleared." % (host, fmetric)
            else:
                fvalue = self.format_value(metric, value)
                return "%s: %s is %s!" % (host, fmetric, fvalue)

    def format_batch(self, batch):
        """Collect failing/restored metrics together to give a simpler
        message if a host simply goes away."""
        msgs = []
        metrics_absent = {}
        metrics_restored = {}

        # pass 1 - count metrics absent versus restored
        for host, metric, value, cleared in batch:
            if value == 'null':
                if cleared:
                    metrics_restored.setdefault(host, set()).add(self.format_name(metric))
                else:
                    metrics_absent.setdefault(host, set()).add(self.format_name(metric))

        # clean up those counts so that we don't just say "3 metrics absent and 5 metrics restored"
        # we need to say what has happened to what at that point
        for k, v in metrics_absent.items()[:]:
            if len(v) <= 1:
                del(metrics_absent[k])
            elif len(metrics_restored.get(k, set())) > 1:
                del(metrics_absent[k])
                del(metrics_restored[k])
        for k, v in metrics_restored.items()[:]:
            if len(v) <= 1:
                del(metrics_restored[k])

        for host, metrics in metrics_absent.items():
            msgs.append("%s: %d metrics are absent! (%s)" % (host, len(metrics), ', '.join(metrics)))
        for host, metrics in metrics_restored.items():
            msgs.append("%s: %d metrics restored. (%s)" % (host, len(metrics), ', '.join(metrics)))

        # output any metrics remaining
        for host, metric, value, cleared in batch:
            if value == 'null' and (host in metrics_restored or host in metrics_absent):
                continue
            msgs.append(self.format_alert(host, metric, value, cleared))
        return msgs

    def lookup_format(self, metric):
        for k in self.namemap:
            if fnmatch.fnmatch(metric, k):
                return self.namemap[k]
        else:
            raise KeyError(k)

    def format_name(self, metric):
        try:
            service, module, metric_name = metric.split('`')
        except ValueError:
            module = ''
        try:
            format = self.lookup_format(metric)
            return format['name'].replace('$m', module)
        except KeyError:
            return metric

    def format_value(self, metric, value):
        if re.match(r'^\d+\.\d+', value):
            value = float(value)
        elif re.match(r'\d+', value):
            value = int(value)

        try:
            format = self.lookup_format(metric)
            return format['valueformat'] % value
        except KeyError:
            return value
        except TypeError:
            print "Error converting value", value, "for metric", metric
            return value
